源码 parse 成 AST 之后,需要进行 AST 的遍历和增删改(transform)。那么 transform 的流程是什么样的?
babel 会递归遍历 AST,遍历过程中处理到不同的 AST 会调用不同的 visitor 函数来实现 transform。这是 visitor 模式的应用。
# visitor模式
visitor 模式(访问者模式)是 23 种经典设计模式中的一种。当被操作的对象结构比较稳定,而操作对象的逻辑经常变化的时候,通过分离逻辑和对象结构,使得他们能独立扩展。这就是 visitor 模式的思想。

如图,Element 和 Visitor 分别代表对象结构和操作逻辑,两者可以独立扩展,在 Client 里面来组合两者,使用 visitor 操作 element。这就是 visitor 模式。
对应到 babel traverse 的实现,就是 AST 和 visitor 分离,在 traverse(遍历)AST 的时候,调用注册的 visitor 来对其进行处理。

这样使得 AST 的结构和遍历算法固定,visitor 可以通过插件独立扩展。
# 路径和作用域
babel AST 中只包含源码的一些信息,但是操作 AST 时要拿到父节点的信息,并且也需要对 AST 增删改的方法,这些都在 path 对象里。

babel 会在 traverse 的过程中在 path 里维护节点的父节点引用,在其中保存 scope(作用域)的信息,同时也会提供增删改 AST 的方法。
# path 的属性
path 大概有这些属性和方法
path {
// 属性:
node
parent
parentPath
scope
hub
container
key
listKey
// 方法
get(key)
set(key, node)
inList()
getSibling(key)
getNextSibling()
getPrevSibling()
getAllPrevSiblings()
getAllNextSiblings()
isXxx(opts)
assertXxx(opts)
find(callback)
findParent(callback)
insertBefore(nodes)
insertAfter(nodes)
replaceWith(replacement)
replaceWithMultiple(nodes)
replaceWithSourceString(replacement)
remove()
traverse(visitor, state)
skip()
stop()
}
它们各自的含义:
- path.node 当前 AST 节点
- path.parent 父 AST 节点
- path.parentPath 父 AST 节点的 path
- path.scope 作用域,见下文详解
- path.hub 可以通过 path.hub.file 拿到最外层 File 对象, path.hub.getScope 拿到最外层作用域,path.hub.getCode 拿到源码字符串
- path.container 当前 AST 节点所在的父节点属性的属性值
- path.key 当前 AST 节点所在父节点属性的属性名或所在数组的下标
- path.listkey 当前 AST 节点所在父节点属性的属性值为数组时 listkey 为该属性名,否则为 undefined
# container、listkey、key(不常用,可略过)
container、listkey、key 这三个属性有点绕(也不太常用,可以跳过,后面实现手写 babel 的时候才会用到),解释一下:
因为 AST 节点要挂在父 AST 节点的某个属性上,那个属性的属性值就是这个 AST 节点的 container。
比如 CallExpression 有 callee 和 arguments 属性,那么对于 callee 的 AST 节点来说,callee 的属性值就是它的 container,而 callee 就是它的 key。

而 BlockStatement 有 body 属性,是一个数组,对于数组中的每一个 AST 来说,这个数组就是它们的 container,而 listKey 是 body,key 则是下标。

# path 的方法
path 有如下方法
- inList() 判断节点是否在数组中,如果 container 为数组,也就是有 listkey 的时候,返回 true
- get(key) 获取某个属性的 path
- set(key, node) 设置某个属性的值
- getSibling(key) 获取某个下标的兄弟节点
- getNextSibling() 获取下一个兄弟节点
- getPrevSibling() 获取上一个兄弟节点
- getAllPrevSiblings() 获取之前的所有兄弟节点
- getAllNextSiblings() 获取之
